﻿namespace EyeOpen.IO
{
    using System;
    using System.Security.Cryptography;

    public class EncryptionService
    {
        private readonly SymmetricEncryptor crypto = new SymmetricEncryptor(SymmetricProvider.Rijndael);
        private readonly CompressionService compressionService = new CompressionService();
        private const string StartEncrypt = "RijndaelManaged;;";
        private const string StartCompress = "Gzip";
        private const string EndEncrypt = ";;End";

        public EncryptionResult CryptDecrypt(string key, string source)
        {
            if (string.IsNullOrWhiteSpace(source))
            {
                throw new ArgumentNullException("source");
            }

            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key");
            }

            if (key.Length < 8)
            {
                throw new ArgumentException("Length of key must be at least 8 characters.", "key");
            }

            if (source.StartsWith(StartEncrypt))
            {
                return Decrypt(source, key);
            }

            return Encrypt(source, key);
        }

        private EncryptionResult Encrypt(string source, string key)
        {
            var encryptionResult = new EncryptionResult();
            string ret;
            var local = source;
            local = local.Replace("[", "[[");
            local = local.Replace("\r", "[r");
            local = local.Replace("\n", "[n");

            var encryptedClearText = crypto.Encrypting(local, key);
            var compressedText = compressionService.Compress(local);
            var encryptedCompressedText = crypto.Encrypting(compressedText, key);

            var vector = crypto.InitializationVector;
            if (encryptedCompressedText.Length < encryptedClearText.Length)
            {
                ret = string.Format("{0}{1}<{2}>{3}{4}", StartEncrypt, StartCompress, vector, encryptedCompressedText, EndEncrypt);
                encryptionResult.EncryptionType = EncryptionType.EncryptedCompressed;
            }
            else
            {
                ret = string.Format("{0}<{1}>{2}{3}", StartEncrypt, vector, encryptedClearText, EndEncrypt);
                encryptionResult.EncryptionType = EncryptionType.EncryptedNotCompressed;
            }

            encryptionResult.Text = ret;
            encryptionResult.ClearTextLenght = source.Length;
            encryptionResult.CompressedTextLenght = encryptedCompressedText.Length + StartCompress.Length;
            encryptionResult.EncryptedTextLenght = encryptedClearText.Length + StartEncrypt.Length + EndEncrypt.Length;

            return encryptionResult;
        }

        private EncryptionResult Decrypt(string source, string key)
        {
            var encryptionResult = new EncryptionResult();
            string ret;
            var mustdecrypt = false;
            var localSource = source.TrimStart(StartEncrypt.ToCharArray());
            localSource = localSource.TrimEnd(EndEncrypt.ToCharArray());

            if (localSource.StartsWith(StartCompress))
            {
                localSource = localSource.TrimStart(StartCompress.ToCharArray());
                mustdecrypt = true;
            }

            var initializationVector = localSource.TrimStart("<".ToCharArray());
            initializationVector = initializationVector.Substring(0, initializationVector.IndexOf('>'));

            try
            {
                ret = crypto.Decrypting(localSource, key, initializationVector);
                if (mustdecrypt)
                {
                    ret = compressionService.Decompress(ret);
                }

                ret = ret.Replace("[r", "\r");
                ret = ret.Replace("[n", "\n");
                ret = ret.Replace("[[", "[");

                encryptionResult.ClearTextLenght = ret.Length;
                encryptionResult.EncryptionType = EncryptionType.Decrypted;
                encryptionResult.Text = ret;
            }
            catch (CryptographicException)
            {
                throw new ArgumentException("Invalid password for decryption.");
            }

            return encryptionResult;
        }
    }
}